package cn.lingyangwl.agile.auth.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.lingyangwl.agile.auth.assembly.*;
import cn.lingyangwl.agile.auth.manager.*;
import cn.lingyangwl.agile.auth.mapper.*;
import cn.lingyangwl.agile.auth.model.entity.*;
import cn.lingyangwl.agile.auth.model.rqrs.client.*;
import cn.lingyangwl.agile.auth.oauth2.model.*;
import cn.lingyangwl.agile.auth.service.*;
import cn.lingyangwl.agile.common.core.exception.enums.*;
import cn.lingyangwl.agile.model.*;
import cn.lingyangwl.agile.model.constants.*;
import cn.lingyangwl.agile.model.enums.*;
import cn.lingyangwl.agile.model.module.tenant.*;
import cn.lingyangwl.agile.system.*;
import cn.lingyangwl.agile.system.constants.*;
import cn.lingyangwl.agile.tenant.core.utils.*;
import cn.lingyangwl.framework.data.mate.annotations.*;
import cn.lingyangwl.framework.mybatis.plus.utils.*;
import cn.lingyangwl.framework.tool.core.*;
import cn.lingyangwl.framework.tool.core.exception.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 *
 * @author shenguangyang
 */
@Service
public class OAuth2ClientServiceImpl
		extends ServiceImpl<OAuth2ClientMapper, OAuth2Client> implements OAuth2ClientService {
	@Resource
	private OAuth2ClientManager OAuth2ClientManager;
	@Resource
	private AuthClientDetailsAssembly authClientDetailsAssembly;
	@Resource
	private ExtLdpConnectService ldpConnectService;
	@Resource
	private AuthMqService mqService;
	@Resource
	@Lazy
	private SystemApi systemApi;


	@Override
	@Cacheable(value = CacheCons.CLIENT_DETAILS_KEY, key = "#clientId + '_clientId'", unless = "#result == null")
	public OAuth2Client getByClientId(String clientId) {
		return TenantUtils.executeIgnore(DataIsolateLevel.ALL, () -> OAuth2ClientManager.getOne(Wrappers.<OAuth2Client>lambdaQuery()
				.eq(OAuth2Client::getClientId, clientId)));
	}

	/**
	 * 清除客户端缓存
	 */
	@Override
	@CacheEvict(value = CacheCons.CLIENT_DETAILS_KEY, allEntries = true)
	public void clearClientCache() {

	}

	/**
	 * 根据主键查询 oauth客户端
	 *
	 * @param id 主键
	 * @return 返回记录，没有返回null
	 */
	@Override
	@FieldBindHandle
	public OAuthClientResp getClient(Long id) {
		String mgtSceneTenantId = systemApi.getMgtSceneTenantId(TenantContext.get().getTenantId());
		return TenantUtils.execute(mgtSceneTenantId, () -> {
			OAuth2Client entity = OAuth2ClientManager.getById(id);
			if (Objects.isNull(entity)) {
				throw new BizException("身份源不存在");
			}
			return authClientDetailsAssembly.toAuthClientDetailsResp(entity);
		});
	}

	@Override
	public List<ClientExtLdpConnResp> listExtLdpConn(Long id) {
		String extGrantTypeStr = Optional.ofNullable(id).map(OAuth2ClientManager::getById)
				.map(OAuth2Client::getExtGrantTypes).orElse("");

		List<String> extGrantTypes = StringUtils.toList(extGrantTypeStr, ",", String.class);

		return ldpConnectService.list().stream().map(e -> {
			ClientExtLdpConnResp extLdpConn = new ClientExtLdpConnResp();
			extLdpConn.setChecked(extGrantTypes.contains(e.getGrantType()));
			extLdpConn.setDisplayName(e.getDisplayName());
			extLdpConn.setName(e.getName());
			extLdpConn.setGrantType(e.getGrantType());
			extLdpConn.setConfigKey(e.getConfigKey());
			return extLdpConn;
		}).collect(Collectors.toList());
	}

	/**
	 * 保存 oauth客户端
	 *
	 * @param req 新增的记录
	 * @return 返回影响行数
	 */
	@Override
	public boolean saveClient(OAuthClientSaveReq req) {
		OAuth2Client entity = authClientDetailsAssembly.toAuthClientDetails(req);

		TenantUtils.executeIgnore(() -> {
			long count = OAuth2ClientManager.count(OAuth2Client.lqw().eq(OAuth2Client::getClientId, req.getClientId()));
			if (count > 0) {
				throw new BizException("客户端已存在");
			}
		});

		return this.save(entity);
	}


	/**
	 * 更新 oauth客户端
	 *
	 * @param req 修改的记录
	 * @return 返回影响行数
	 */
	@Override
	@CacheEvict(value = CacheCons.CLIENT_DETAILS_KEY, allEntries = true)
	public boolean updateClient(OAuthClientUpdateReq req) {
		OAuth2Client client = this.getById(req.getId());
		if (Objects.isNull(client)) {
			throw new BizException(GlobalErrorEnum.OPERATION_FAIL_SELETE);
		}
		OAuth2Client entity = authClientDetailsAssembly.toAuthClientDetails(req);
		boolean ret = OAuth2ClientManager.updateById(entity);
		mqService.deleteClientAuthority(client.getClientId());
		return ret;
	}

	/**
	 * 通过id删除 oauth客户端
	 *
	 * @param id 删除的主键
	 * @return 返回影响行数
	 */
	@Override
	@CacheEvict(value = CacheCons.CLIENT_DETAILS_KEY, allEntries = true)
	public boolean deleteClient(Long id) {
		OAuth2Client client = this.getById(id);
		if (Objects.isNull(client)) {
			return true;
		}
		boolean ret = OAuth2ClientManager.removeById(id);
		mqService.deleteClientAuthority(client.getClientId());
		return ret;
	}

	/**
	 * 通过ids删除 oauth客户端
	 *
	 * @param ids 删除的主键
	 * @return 返回影响行数
	 */
	@Override
	@CacheEvict(value = CacheCons.CLIENT_DETAILS_KEY, allEntries = true)
	public boolean batchDeleteClient(List<Long> ids) {
		List<String> clientIds = this.listByIds(ids).stream().map(OAuth2Client::getClientId).collect(Collectors.toList());
		boolean ret = OAuth2ClientManager.removeByIds(ids);
		clientIds.forEach(mqService::deleteClientAuthority);
		return ret;
	}

	/**
	 * 分页查询 oauth客户端
	 *
	 * @return 返回分页数据
	 */
	@Override
	@FieldBindHandle
	public IPage<OAuthClientResp> pageClient(OAuthClientPageReq req) {
		LambdaQueryWrapper<OAuth2Client> queryWrapper = Wrappers.<OAuth2Client>lambdaQuery()
				.like(StringUtils.isNotEmpty(req.getClientId()), OAuth2Client::getClientId, req.getClientId())
				.orderByDesc(BasePlusPO::getCreatedAt);
		IPage<OAuth2Client> pageData = OAuth2ClientManager.page(PageUtil.to(req), queryWrapper);
		return PageUtil.to(pageData, authClientDetailsAssembly::toAuthClientDetailsResp);
	}

	@Override
	public OAuthClientListResp listClient(OAuthClientListReq req) {
		String extGrantType = StringUtils.defaultIfEmpty(req.getExtGrantType(), "-1");
		List<OAuthClientResp> list = this.OAuth2ClientManager.list().stream()
				.map(authClientDetailsAssembly::toAuthClientDetailsResp)
				.collect(Collectors.toList());

		List<Long> checkedList = list.stream()
				.filter(e -> StringUtils.isNotEmpty(e.getExtGrantTypes()))
				.filter(e -> StringUtils.toList(e.getExtGrantTypes(), ",", String.class).contains(extGrantType))
				.map(OAuthClientResp::getId).collect(Collectors.toList());

		OAuthClientListResp resp = new OAuthClientListResp();
		resp.setList(list);
		resp.setCheckedList(checkedList);
		return resp;
	}


	@Override
	public RegisteredClient validOAuthClient(String clientId,
											 String clientSecret, String authorizedGrantType,
											 Collection<String> scopes, String redirectUri) {
		return TenantUtils.executeIgnore(DataIsolateLevel.ALL, () -> {
			OAuth2Client client = this.getByClientId(clientId);
			if (Objects.isNull(client)) {
				throw new BizException(AuthErrorEnum.CLIENT_NOT_EXIST);
			}

			// 校验客户端密钥
			if (StrUtil.isNotEmpty(clientSecret) && ObjectUtil.notEqual(client.getClientSecret(), clientSecret)) {
				throw new BizException(AuthErrorEnum.OAUTH2_CLIENT_CLIENT_SECRET_ERROR);
			}

			// 校验授权方式
			List<String> extGrantTypes = StringUtils.toList(client.getExtGrantTypes(), ",", String.class);
			client.getAuthorizedGrantTypes().addAll(extGrantTypes);
			if (StrUtil.isNotEmpty(authorizedGrantType) && !CollUtil.contains(client.getAuthorizedGrantTypes(), authorizedGrantType)) {
				throw new BizException(AuthErrorEnum.OAUTH2_GRANT_TYPE_NOT_EXISTS, authorizedGrantType);
			}

			// 校验授权范围
			if (CollUtil.isNotEmpty(scopes) && !CollUtil.containsAll(client.getScope(), scopes)) {
				throw new BizException(AuthErrorEnum.OAUTH2_CLIENT_SCOPE_OVER);
			}

			RegisteredClient registeredClient = RegisteredClient.of(client);
			Optional.ofNullable(scopes).ifPresent(
					scope -> scope.stream().filter(StrUtil::isNotBlank).forEach(registeredClient::scope));

			return registeredClient;
		});
	}

	@Override
	@CacheEvict(value = CacheCons.CLIENT_DETAILS_KEY, allEntries = true)
	public void refreshClientAuthorityByRoleIds(List<Long> roleIds, boolean roleDeleted) {
		List<OAuth2Client> list = this.list();
		List<String> clientIds = new ArrayList<>();
		for (OAuth2Client client : list) {
			List<Long> clientRoleIds = Optional.ofNullable(client.getRoleIds()).orElse(Collections.emptyList());
			if (clientRoleIds.stream().anyMatch(roleIds::contains)) {
				if (roleDeleted) {
					clientRoleIds.removeAll(roleIds);
				}
				clientIds.add(client.getClientId());
				client.setRoleIds(clientRoleIds);
			}
		}
		this.updateBatchById(list);
		clientIds.forEach(mqService::deleteClientAuthority);
	}
}
